package com.gitee.mocoffee.json;

import java.io.IOException;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.concurrent.ConcurrentHashMap;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.BeanContext;
import com.alibaba.fastjson.serializer.ContextObjectSerializer;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializeFilter;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature;

/**
 * @author lijia
 */
public class JsonKit {
    /**
     * 一个默认的日期时间序列化器
     */
    private static class DateTimeSerializer implements ContextObjectSerializer {
        public static final DateTimeSerializer me = new DateTimeSerializer();

        private final static String datePattern      = "yyyy-MM-dd";
        private final static String iso8601Pattern   = "yyyy-MM-dd'T'HH:mm:ss";
        private final static String datetimePattern  = "yyyy-MM-dd HH:mm:ss";
        private final static String timePattern      = "HH:mm:ss";
        private final static String yearmonthPattern = "yyyy-MM";
        private final static String monthdayPattern  = "MM-dd";

        private static final ConcurrentHashMap<String, DateTimeFormatter> cache;

        static {
            cache = new ConcurrentHashMap<>(16);
            String[] patterns = new String[]{datePattern, iso8601Pattern, datetimePattern, timePattern,
                    yearmonthPattern, monthdayPattern};
            for (String p : patterns) {
                cache.put(p, DateTimeFormatter.ofPattern(p));
            }
        }

        private void write(SerializeWriter out, TemporalAccessor object, String format) {
            DateTimeFormatter formatter = cache.get(format);
            if (formatter == null) {
                formatter = DateTimeFormatter.ofPattern(format);
                cache.put(format, formatter);
            }

            String text = formatter.format(object);
            out.writeString(text);
        }

        @Override
        public void write(JSONSerializer serializer, Object object, BeanContext context) throws IOException {
            SerializeWriter out = serializer.out;
            String format = context.getFormat();
            if (format == null || format.isEmpty())
                format = datetimePattern;
            write(out, (TemporalAccessor) object, format);
        }

        @Override
        public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
                throws IOException {
            SerializeWriter out = serializer.out;
            if (object == null) {
                out.writeNull(0, features);
            } else {
                String format = serializer.getDateFormatPattern();
                if (format == null || format.isEmpty()) {
                    if (object instanceof LocalDate) {
                        format = datePattern;
                    } else if (object instanceof LocalDateTime) {
                        boolean useISO8601 = (features & SerializerFeature.UseISO8601DateFormat.getMask()) != 0
                                || serializer.isEnabled(SerializerFeature.UseISO8601DateFormat);

                        if (useISO8601)
                            format = iso8601Pattern;
                        else
                            format = datetimePattern;
                    } else if (object instanceof LocalTime) {
                        format = timePattern;
                    } else if (object instanceof YearMonth) {
                        format = yearmonthPattern;
                    } else if (object instanceof MonthDay) {
                        format = monthdayPattern;
                    } else
                        format = datetimePattern;
                }

                write(out, (TemporalAccessor) object, format);
            }
        }
    }

    public static final JsonKit me = new JsonKit();

    private SerializeConfig     config;
    private SerializeFilter[]   filters;
    private SerializerFeature[] features;
    private int                 defaultFeatures;
    private String              defaultDatePattern;

    private JsonKit() {
        this.config = SerializeConfig.getGlobalInstance();
        this.config.put(LocalDate.class, DateTimeSerializer.me);
        this.config.put(LocalDateTime.class, DateTimeSerializer.me);
        this.config.put(LocalTime.class, DateTimeSerializer.me);
        this.config.put(YearMonth.class, DateTimeSerializer.me);
        this.config.put(MonthDay.class, DateTimeSerializer.me);
        this.filters = new SerializeFilter[0];
        this.features = new SerializerFeature[0];
        this.defaultFeatures = SerializerFeature.QuoteFieldNames.mask
                | SerializerFeature.WriteEnumUsingToString.mask
                | SerializerFeature.SkipTransientField.mask
                | SerializerFeature.DisableCircularReferenceDetect.mask
                | SerializerFeature.WriteDateUseDateFormat.mask;
        this.defaultDatePattern = null;
    }

    public String toJson(Object object) {
        return JSON.toJSONString(object, this.config, this.filters, this.defaultDatePattern, this.defaultFeatures,
                                 this.features);
    }

    public String toJson(Object object, SerializeConfig config, SerializeFilter[] filters, String datePattern,
                         SerializerFeature[] features) {
        return JSON.toJSONString(object, config, filters, datePattern, defaultFeatures, features);
    }

    public String toJsonWithDefault(Object object, SerializeConfig config, SerializeFilter[] filters,
                                    String datePattern, SerializerFeature[] features) {
        return JSON.toJSONString(object, config == null ? this.config : config,
                                 filters == null ? this.filters : filters,
                                 datePattern == null ? this.defaultDatePattern : datePattern,
                                 defaultFeatures, features == null ? this.features : features);
    }

    public String toJson(Object object, String datePattern) {
        return toJsonWithDefault(object, null, null, datePattern, null);
    }

    public String toJson(Object object, SerializeFilter[] filters) {
        return toJsonWithDefault(object, null, filters, null, null);
    }
}
